package cn.rengy.web.framework.wxmp.token;

import java.time.Duration;

import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import cn.rengy.tool.core.util.ExceptionPrintUtils;
import cn.rengy.lang.WeChatException;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public abstract class AbstractTokenService implements TokenService{
	@Autowired
	private StringRedisTemplate stringRedisTemplate;
	@Autowired
	private RedissonClient redissonClient;
	//@Autowired
	//private AccessTokenRefreshService accessTokenRefreshService;
	
	
	/**
	 * 获取token
	 * @param appid
	 * @return
	 * @throws Exception 
	 */
	public String acquire(String appid) throws WeChatException{
		//String appid=accessToken.getAppId();
		String key=getAccessKey(appid);
		/**
		 * 1.从缓存中获取
		 * 2.有 返回
		 * 3.无 刷新缓存 再次获取返回
		 */
		ValueOperations<String, String> vo=stringRedisTemplate.opsForValue();
		String token=vo.get(key);
		//logger.debug("从缓存中获取{}的accessToken结果{}",key,token);
		if(token==null){
			refresh(appid);
			token=vo.get(key);
		}
		if(token==null){
			throw new WeChatException(-1,"获取token失败");
		}
		return token;
	}
	
	/**
	 * 当token不可用时删除
	 * @param appid
	 */
	public void evict(String appid,String value){
		//和获取token使用同一把锁
		RLock lock=redissonClient.getLock(getLockKey(appid));
		//当token失效时先删除
		try {
			lock.lock();
			//判断token是否为空
			String key=getAccessKey(appid);
			String token=stringRedisTemplate.opsForValue().get(key);
			if(token!=null&&token.equals(value)){
				stringRedisTemplate.delete(key);
				//当token刷新后,ticket是否可用？
			}
		}catch(Exception e) {
			log.error(ExceptionPrintUtils.getTrace(e));
		} finally {
		   lock.unlock();
		}
	}
	/**
	 * 刷新Token
	 * @param appid
	 * @throws WeChatException
	 */
	public void refresh(String appid) throws WeChatException{
		/**
		 * 逻辑约定了当执行获取token的时候，redis中的token肯定为空， （1第一次使用2失效过期3返回40001先删除）
		 * 
		 * 所以当获取到锁后只要判断token不为空就可以退出，不执行获取
		 */
		//使用redislock分布式锁防止并发执行刷新，必须同时结合token为空判断，之前的方案是判断时间是否最近更新也是可以的
		RLock lock=redissonClient.getLock(getLockKey(appid));
		try {
			lock.lock();
			//判断token是否为空
			String key=getAccessKey(appid);
			String token=stringRedisTemplate.opsForValue().get(key);
			if(StringUtils.isBlank(token)){
				TokenDTO tokenDTO=newToken(appid);
				saveToken(key,tokenDTO);
				//更新access_token后ticket是否有效？无效则删除jsapiticket
				//tokenRedisService.evictJsApiTicket(wx_account_id);
			}
		}catch(WeChatException e) {
			throw e;
		}catch(Exception e) {
			log.error(ExceptionPrintUtils.getTrace(e));
		} finally {
		   lock.unlock();
		}
	}
	void saveToken(String key,TokenDTO tokenDTO) {
		//加入缓存 提前120秒刷新 前提 有效期小时级别
		stringRedisTemplate.opsForValue().set(key, tokenDTO.getToken(),Duration.ofSeconds(tokenDTO.getExpires()-120));
	}
	@Getter
	@Setter
	class TokenDTO{
		private String token;
		private int expires;
	}
	
	/**
	 * 获取一个新的token
	 * @param appid
	 * @return
	 */
	abstract TokenDTO newToken(String appid);
	
	
	abstract void afterSaveToken();
}
